<template>
    <tnxel-submit-form ref="form" :model="model" inline :rules="rules">
        <div class="flex-v-center">
            <el-form-item label="abstract" label-width="74px">
                <tnxel-enum-view type="Boolean" :item-key="String(clazz.abstractClass)"/>
            </el-form-item>
            <el-form-item label="父类" label-width="44px" v-if="clazz.superClassId">
                <ClassNameView :app="app"
                    :class-id="clazz.superClassId"
                    @open-class="$emit('open-class', $event)"
                    @open-update="$emit('open-update', clazz)"
                />
            </el-form-item>
            <el-form-item label="泛型具化" v-if="clazz.instantiatedGenericTypes?.length">
                <GenericTypeDeclare v-model="model.instantiatedGenericTypes"
                    :app="app"
                    :updatable="false"
                />
            </el-form-item>
            <el-form-item label="继承映射策略" prop="inheritanceStrategy" label-width="100px">
                <div v-if="superClassWithInheritanceStrategy">
                    <span>{{ superClassWithInheritanceStrategy.inheritanceStrategy.caption }}</span>
                    <span class="text-placeholder">（由父类决定）</span>
                </div>
                <tnxel-enum-select v-model="model.inheritanceStrategy"
                    style="width: 120px;"
                    type="InheritanceStrategy"
                    placeholder=""
                    empty
                    v-else>
                    <template #option="{item}">
                        <div class="flex-v-center">
                            <tnxel-icon value="QuestionFilled" :title="item.desc"/>
                            <span>{{ item.caption }}</span>
                        </div>
                    </template>
                </tnxel-enum-select>
            </el-form-item>
            <el-form-item label=" " label-width="10px" class="flex-1 me-0">
                <div class="w-100 flex-v-center justify-content-end">
                    <tnxel-button type="primary"
                        icon="fa-regular fa-floppy-disk"
                        :loading="saving"
                        :disabled="app.dependencyInitializing"
                        disabled-tip="依赖库加载中，需加载完毕后才可保存"
                        @click="toSave"
                    >
                        保存 (Ctrl+S)
                    </tnxel-button>
                    <tnxel-button type="default"
                        class="ms-2"
                        icon="bi bi-code-slash"
                        :key="clazz.modified"
                        :disabled="app.dependencyInitializing || clazz.modified"
                        :disabled-tip="app.dependencyInitializing ? '依赖库加载中，需加载完毕后才可生成代码' : '已改动，需保存后才可生成代码'"
                        @click="toGenerateCode"
                    >
                        生成代码
                    </tnxel-button>
                </div>
            </el-form-item>
        </div>
        <div class="flex-v-center">
            <el-form-item label="表名" prop="tableName" class="new-error-line" label-width="44px">
                <div class="text-placeholder" v-if="!tableEnabled">
                    抽象类在当前继承映射策略下，没有对应数据库表
                </div>
                <div v-else-if="superInheritanceStrategyIsSingleTable">
                    <span>{{ superClassWithInheritanceStrategy.tableName || defaultTableName }}</span>
                    <span class="text-placeholder">（由父类决定）</span>
                </div>
                <div class="flex-v-center" v-else>
                    <el-input v-model="model.tableName" :placeholder="'默认：' + defaultTableName">
                        <template #append>
                            <tnxel-button icon="Finished" title="填入默认表名" @click="autoTableName"/>
                        </template>
                    </el-input>
                    <el-tooltip placement="top" content="默认表名生成规则在工程设置中的设定，点击打开工程设置">
                        <tnxel-icon class="ms-2 text-placeholder cursor-pointer" value="QuestionFilled"
                            @click="$emit('open-setting')"/>
                    </el-tooltip>
                </div>
            </el-form-item>
            <template v-if="discriminatorColumnRequired">
                <el-form-item label="鉴别字段名称" class="new-error-line" prop="discriminatorName">
                    <el-input v-model="model.discriminatorName" placeholder="建议以_开头，以示特殊"/>
                </el-form-item>
                <el-form-item label="鉴别字段类型" prop="discriminatorType">
                    <tnxel-enum-select v-model="model.discriminatorType" type="DiscriminatorType"
                        style="width: 100px;"/>
                </el-form-item>
                <el-form-item label="鉴别字段长度" prop="discriminatorLength"
                    v-if="model.discriminatorType !== DiscriminatorType.INTEGER.name">
                    <tnxel-input-number v-model="model.discriminatorLength" :min="1" style="width: 100px;"/>
                </el-form-item>
                <span class="form-item-tip mb-3">鉴别值在各子类中分别指定</span>
            </template>
        </div>
        <el-form-item label="继承鉴别值" prop="discriminatorValue" class="new-error-line" label-width="97px"
            v-if="superInheritanceStrategyIsSingleTable">
            <el-input v-model="model.discriminatorValue" placeholder="请输入" style="width: 100px;"/>
            <span class="ms-2 text-placeholder" v-if="otherDiscriminatorValues.length">
                    其它已用值：{{ otherDiscriminatorValues.join(', ') }}
                </span>
        </el-form-item>
        <el-form-item label="属性集" class="w-100 me-0 mb-1" label-width="58px">
            <div class="flex-v-center w-100 justify-content-end">
                <div class="row-child text-secondary px-2 ms-2"
                    v-if="model.properties?.some(p => p.children?.length)">
                    来自值模型的属性
                </div>
                <div class="row-derived text-secondary px-2 ms-2"
                    v-if="model.properties?.some(p => p.derived)">
                    派生属性
                </div>
            </div>
        </el-form-item>
        <tnxel-edit-table ref="propertiesTable"
            :data="model.properties"
            add-text="添加属性"
            :new-row="newProperty"
            row-key="ordinal"
            :row-class-name="rowClassName"
            :rules="propertyRules"
            :sortable="isSortableRow"
            :removable="isRemovableRow"
            @sorted="resetOrdinals"
            @removed="resetOrdinals"
        >
            <el-table-column label="序号" header-align="center" :align="hasChildren ? undefined : 'center'" width="80">
                <template #default="{ row }">
                    <ColorableIndex :index="row.ordinal" v-model:color="row.color"
                        :updatable="!row.parentPropertyName"/>
                </template>
            </el-table-column>
            <el-table-column label="显示名称" prop="caption" width="150">
                <template #default="{ row }">
                    <span v-if="row.primary"></span>
                    <el-input v-model="row.caption" v-else/>
                </template>
            </el-table-column>
            <el-table-column label="逻辑属性名" prop="name">
                <template #default="{ row }">
                    <span class="mx-2"
                        :title="row.name"
                        v-if="row.parentPropertyName">
                        {{ row.name }}
                    </span>
                    <el-input v-model="row.name"
                        :disabled="row.primary && TypeKind.UNITY.equals(clazz.kind)"
                        @input="onLogicNameChange(row)"
                        v-else/>
                </template>
            </el-table-column>
            <el-table-column label="逻辑属性类型" prop="type" width="150">
                <template #default="{ row }">
                    <LogicTypeSelect class="w-100"
                        v-model="row.typeId"
                        :app="app"
                        :read-only="!!row.parentPropertyName"
                        :kind="clazz.kind"
                        :primary="row.primary"
                        :key="model.properties.length"
                        @change="onLogicTypeChange(row)"
                    />
                </template>
            </el-table-column>
            <el-table-column label="泛型" prop="genericTypes" width="160">
                <template #default="{ row }">
                    <GenericTypeSpecify v-model="row.genericTypes"
                        :app="app"
                        :logic-type="row.typeId"
                        :updatable="!row.parentPropertyName"
                        @change="onGenericTypeChange(row)"
                    />
                </template>
            </el-table-column>
            <el-table-column label="数组" prop="arrayed" align="center" width="60">
                <template #default="{ row }">
                    <ArrayedSelect v-model="row.arrayed" :property="row"/>
                </template>
            </el-table-column>
            <el-table-column prop="nature" header-align="center" width="80">
                <template #header>
                    <span>性质</span>
                    <el-tooltip
                        content="派生属性是基于其它属性的值动态计算得出的属性，其计算算法在生成代码后手工实现"
                        placement="top"
                    >
                        <tnxel-icon value="QuestionFilled" class="ms-1"/>
                    </el-tooltip>
                </template>
                <template #default="{ row }">
                    <span v-if="row.children?.length"></span>
                    <div class="flex-v-center ms-2" v-else-if="row.primary && !TypeKind.ENTITY.equals(clazz.kind)">
                        <tnxel-icon value="Key" class="text-primary"/>
                        <span>{{ row.nature_caption }}</span>
                    </div>
                    <span class="flex-v-center ms-2 ps-1" v-else-if="row.parentPropertyName">
                        {{ row.nature_caption }}
                    </span>
                    <tnxel-enum-select v-model="row.nature"
                        type="ClassPropertyNature"
                        :subtype="TypeKind.ENTITY.equals(clazz.kind) ? 'entity' : 'default'"
                        @change="onNatureChange(row)"
                        v-else/>
                </template>
            </el-table-column>
            <el-table-column label="物理字段名" prop="mappingName">
                <template #default="{ row }">
                    <span v-if="row.derived"></span>
                    <el-input v-model="row.mappingName" @input="onPhysicalNameChange(row)" v-else>
                        <template #append v-if="row.children?.length">
                            <el-tooltip content="作为下级属性的数据库字段名的统一前缀" placement="top">
                                <tnxel-icon value="QuestionFilled"/>
                            </el-tooltip>
                        </template>
                        <template #append v-else-if="app.getCollectionElementRefClass(row)">
                            <el-tooltip content="引用类反向关联当前类时的外键字段" placement="top">
                                <tnxel-icon value="QuestionFilled"/>
                            </el-tooltip>
                        </template>
                    </el-input>
                </template>
            </el-table-column>
            <el-table-column label="物理字段类型" prop="mappingType" width="130">
                <template #default="{ row }">
                    <span v-if="row.children?.length || row.derived"></span>
                    <PhysicalTypeSelect :ref="'mappingTypeSelect_' + row.ordinal"
                        v-model="row.mappingType"
                        :app="app"
                        :logic-type="app.getCollectionElementRefClass(row) || row.typeId"
                        :arrayed="row.arrayed"
                        :disabled="TypeKind.UNITY.equals(row.refKind)"
                        @change="onPhysicalTypeChange(row.ordinal)"
                        v-else/>
                </template>
            </el-table-column>
            <el-table-column label="长度" prop="mappingLength" align="center" width="80">
                <template #default="{ row }">
                    <span v-if="row.children?.length || row.derived"></span>
                    <tnxel-input-number v-model="row.mappingLength"
                        :min="0"
                        placeholder=" "
                        :disabled="(row.refKind && row.refKind.supports('standard')) || lengthDisabled[row.ordinal]"
                        @change="onLengthChange(row)"
                        v-else/>
                </template>
            </el-table-column>
            <el-table-column label="精度" prop="mappingScale" align="center" width="70">
                <template #default="{ row }">
                    <span v-if="row.children?.length || row.derived"></span>
                    <tnxel-input-number v-model="row.mappingScale"
                        :min="0"
                        placeholder=" "
                        :disabled="(row.refKind && row.refKind.supports('standard')) || scaleDisabled[row.ordinal]"
                        v-else/>
                </template>
            </el-table-column>
            <el-table-column label="可空" prop="mappingNullable" align="center" width="60">
                <template #default="{ row }">
                    <span v-if="row.children?.length || row.derived"></span>
                    <el-checkbox v-model="row.mappingNullable"
                        :disabled="row.primary || TypeKind.PRIMITIVE.equals(row.refKind)"
                        v-else/>
                </template>
            </el-table-column>
            <el-table-column width="140" align="center">
                <template #header>
                    <span>高级设置</span>
                    <el-tooltip
                        content="由用户填写的字段不允许输入特殊字符"
                        placement="top"
                    >
                        <tnxel-icon value="QuestionFilled" class="ms-1"/>
                    </el-tooltip>
                </template>
                <template #default="{ row }">
                    <div class="advanced-setting"
                        :class="{'is-disabled': app.dependencyInitializing}"
                        v-if="!row.primary && !row.children?.length">
                        <div class="col flex-center">
                            <!-- 用户填写 -->
                            <div class="flex-v-center"
                                :title="row.manualInput ? '由用户填写' : '非用户填写'"
                                v-if="isManualInputEnabled(row)">
                                <tnxel-icon class="text-placeholder"
                                    :value="row.manualInput ? 'UserFilled' : 'User'"
                                    v-if="row.parentPropertyName"/>
                                <tnxel-button
                                    tooltip="点击切换"
                                    :tooltip-show-after="600"
                                    type="primary"
                                    link
                                    :icon="row.manualInput ? 'UserFilled' : 'User'"
                                    @click="row.manualInput = !row.manualInput"
                                    v-else/>
                            </div>
                            <!-- 是否子项：可关联类型且不是值模型的属性 -->
                            <el-popconfirm width="360px"
                                :title="row.belongs ? `当前类不再作为 ${row.typeName} 的子项类？` : `确定当前类是 ${row.typeName} 的子项类吗？`"
                                @confirm="row.belongs = !row.belongs"
                                v-else-if="row.refKind?.supports('association') && !row.parentPropertyName">
                                <template #reference>
                                    <tnxel-icon class="link"
                                        :value="row.belongs ? 'bi bi-menu-app-fill' : 'bi bi-menu-app'"
                                        :title="row.belongs ? '所属上级项' : '非所属上级项'"
                                    />
                                </template>
                            </el-popconfirm>
                            <!-- 转换器 -->
                            <ConverterSelect v-model="row.mappingConverter" :app="app" :property="row"
                                v-else-if="!row.derived"/>
                        </div>
                        <div class="col flex-center">
                            <!-- 行政区划 -->
                            <div class="flex-v-center"
                                :title="row.regional ? '行政区划字段' : '非行政区划字段'"
                                v-if="isRegionalEnabled(row)">
                                <tnxel-icon class="text-placeholder"
                                    :value="row.regional ? 'bi bi-pin-map-fill' : 'bi bi-pin-map'"
                                    v-if="row.parentPropertyName"/>
                                <tnxel-button
                                    tooltip="点击切换"
                                    :tooltip-show-after="600"
                                    type="primary"
                                    link
                                    :icon="row.regional ? 'bi bi-pin-map-fill' : 'bi bi-pin-map'"
                                    @click="row.regional = !row.regional"
                                    v-else/>
                            </div>
                        </div>
                        <div class="col flex-center">
                            <!-- 查询模型 -->
                            <!-- 与其它高级设置图标保持结构一致，以便于统一样式 -->
                            <div class="el-dropdown" v-if="isQueryingEnabled(row)">
                                <el-tooltip content="点击修改" placement="top" :show-after="600"
                                    :disabled="app.dependencyInitializing">
                                    <el-badge type="primary" is-dot :hidden="!row.mappingQuerying">
                                        <tnxel-icon class="link"
                                            value="Search"
                                            :title="row.mappingQuerying ? '已作为查询条件字段' : '不作为查询条件字段'"
                                            @click="toPropertyQuerying(row)"
                                        />
                                    </el-badge>
                                </el-tooltip>
                            </div>
                        </div>
                        <div class="col flex-center">
                            <!-- 命令模型 -->
                            <div class="el-dropdown" v-if="isCommandEnabled(row)">
                                <el-tooltip content="点击修改" placement="top" :show-after="600"
                                    :disabled="app.dependencyInitializing">
                                    <el-badge type="primary" is-dot :hidden="!row.mappingCommand">
                                        <tnxel-icon class="link"
                                            value="bi bi-input-cursor"
                                            :title="row.mappingCommand ? '已作为命令模型属性' : '不作为命令模型属性'"
                                            @click="toPropertyCommand(row)"
                                        />
                                    </el-badge>
                                </el-tooltip>
                            </div>
                        </div>
                        <div class="col flex-center">
                            <!-- 默认值 -->
                            <PhysicalDefaultValueSelect v-model="row.mappingDefaultValue"
                                :app="app"
                                :type="row.mappingType"
                                v-if="isPhysicalTypeDefaultValueEnable(row)"/>
                        </div>
                    </div>
                </template>
            </el-table-column>
        </tnxel-edit-table>
        <template v-if="tableEnabled">
            <el-form-item label="索引集" class="w-100 me-0 mb-1 mt-3" label-width="58px">
                <el-switch v-model="showIndexesTable"/>
            </el-form-item>
            <tnxel-edit-table ref="indexesTable"
                class="mb-3"
                :data="model.indexes"
                :rules="indexRules"
                add-text="添加索引"
                :new-row="newIndex"
                sortable
                v-if="showIndexesTable">
                <el-table-column label="序号" type="index" align="center" width="60"/>
                <el-table-column label="名称" prop="name" width="300">
                    <template #default="{ row }">
                        <el-input v-model="row.name"/>
                    </template>
                </el-table-column>
                <el-table-column label="类型" prop="type" align="center" width="100">
                    <template #default="{ row }">
                        <tnxel-enum-select v-model="row.type" type="PhysicalIndexType"/>
                    </template>
                </el-table-column>
                <el-table-column label="字段" prop="mappingId">
                    <template #default="{ row }">
                        <PhysicalFieldSelect v-model="row.mappingIds" :fields="mappings" title="选择索引字段"/>
                    </template>
                </el-table-column>
            </tnxel-edit-table>
        </template>
    </tnxel-submit-form>
</template>

<script>
import tnx from '@/tnx.js';
import TypeKind from '@/logic/modeling/enums/TypeKind.js';
import EntityClass from '@/logic/modeling/entity/EntityClass.js';
import EntityProperty from '@/logic/modeling/entity/EntityProperty.js';
import DiscriminatorType from '@/logic/modeling/enums/DiscriminatorType.js';
import InheritanceStrategy from '@/logic/modeling/enums/InheritanceStrategy.js';
import PhysicalTypeNecessity from '@/logic/modeling/enums/PhysicalTypeNecessity.js';
import PhysicalIndexType from '@/logic/modeling/enums/PhysicalIndexType.js';
import ClassPropertyNature from '@/logic/modeling/enums/ClassPropertyNature.js';
import ModelingApp from '@/logic/modeling/entity/ModelingApp.js';
import PersistUtil from '@/logic/modeling/util/persist.js';
import {STRING_CLASS_NAME} from '@/logic/modeling/util/dependency-lib-jdk.js';
import {getDefaultPhysicalType} from '@/logic/modeling/util/database-physical-types.js';

import ColorableIndex from './components/ColorableIndex.vue';
import ClassNameView from './components/ClassNameView.vue';
import LogicTypeSelect from './components/LogicTypeSelect.vue';
import GenericTypeSpecify from './components/GenericTypeSpecify.vue';
import PhysicalTypeSelect from './components/PhysicalTypeSelect.vue';
import PhysicalFieldSelect from './components/PhysicalFieldSelect.vue';
import generateCode from './generate-code.vue';
import GenericTypeDeclare from './components/GenericTypeDeclare.vue';
import ConverterSelect from './components/ConverterSelect.vue';
import PhysicalDefaultValueSelect from './components/PhysicalDefaultValueSelect.vue';
import ArrayedSelect from './components/ArrayedSelect.vue';
import propertyQuerying from './entity-property-querying.vue';
import propertyCommand from './entity-property-command.vue';

export default {
    name: 'EntityClassEdit',
    components: {
        ColorableIndex,
        GenericTypeDeclare,
        ClassNameView,
        LogicTypeSelect,
        GenericTypeSpecify,
        PhysicalTypeSelect,
        PhysicalFieldSelect,
        ConverterSelect,
        PhysicalDefaultValueSelect,
        ArrayedSelect,
    },
    props: {
        app: {
            type: ModelingApp,
            required: true
        },
        clazz: {
            type: EntityClass,
            required: true,
        },
        save: {
            type: Function,
            required: true,
        },
        active: {
            type: Boolean,
            required: true,
        },
    },
    emits: ['initialized', 'open-setting', 'open-class', 'open-update'],
    data() {
        return {
            TypeKind, DiscriminatorType, InheritanceStrategy,
            model: this.getModel(),
            initialized: false,
            propertyRules: {
                caption: [tnx.app.validator.manualInputRule],
                name: [tnx.app.validator.requiredRule, tnx.app.validator.symbolRule],
                type: [tnx.app.validator.requiredRule],
                mappingName: [tnx.app.validator.symbolRule],
            },
            lengthDisabled: {},
            lengthConfirmed: {},
            scaleDisabled: {},
            saving: false,
            showIndexesTable: false,
        };
    },
    computed: {
        project() {
            return tnx.app.global.modelingProject;
        },
        discriminatorColumnRequired() {
            return this.model.inheritanceStrategy === InheritanceStrategy.SINGLE_TABLE.name;
        },
        superClassWithInheritanceStrategy() {
            return this.app.getSuperClassWithInheritanceStrategy(this.clazz);
        },
        superInheritanceStrategyIsSingleTable() {
            return InheritanceStrategy.SINGLE_TABLE.equals(this.superClassWithInheritanceStrategy?.inheritanceStrategy);
        },
        otherDiscriminatorValues() {
            let values = [];
            this.project.loopSubClasses(this.superClassWithInheritanceStrategy, clazz => {
                if (!this.clazz.matches(clazz) && clazz.discriminatorValue) {
                    values.push(clazz.discriminatorValue);
                }
            });
            return values;
        },
        tableEnabled() {
            // 抽象类未指定继承映射策略或策略为每类一表，则表名不可用
            if (this.clazz.abstractClass && (!this.model.inheritanceStrategy
                || this.model.inheritanceStrategy === InheritanceStrategy.TABLE_PER_CLASS.name)) {
                return false;
            }
            return true;
        },
        defaultTableName() {
            return this.app.getTableName(this.clazz);
        },
        hasChildren() {
            return this.model.properties?.some(property => property.children?.length);
        },
        rules() {
            let rules = {};
            if (this.discriminatorColumnRequired) {
                rules.discriminatorName = [tnx.app.validator.requiredRule, tnx.app.validator.symbolRule];
            }
            if (this.superInheritanceStrategyIsSingleTable) {
                let vm = this;
                rules.discriminatorValue = [tnx.app.validator.requiredRule, tnx.app.validator.manualInputRule, {
                    trigger: 'blur',
                    validator(r, fieldValue, callback, source, options) {
                        if (vm.otherDiscriminatorValues.includes(fieldValue)) {
                            callback(new Error('其它类已使用该鉴别值'));
                        } else {
                            callback();
                        }
                    },
                }];
            } else {
                rules.tableName = [tnx.app.validator.symbolRule];
            }
            return rules;
        },
        indexRules() {
            return {
                name: [tnx.app.validator.requiredRule, tnx.app.validator.symbolRule],
            };
        },
        mappings() {
            let mappings = [];
            for (let property of this.model.properties) {
                mappings.push({
                    id: property.mappingId,
                    name: property.mappingName,
                });
                if (property.children) {
                    for (let child of property.children) {
                        mappings.push({
                            id: child.mappingId,
                            name: child.mappingName,
                        });
                    }
                }
            }
            return mappings;
        },
    },
    watch: {
        active() {
            if (this.active) {
                window.addEventListener('keydown', this.onKeydown);
            } else {
                window.removeEventListener('keydown', this.onKeydown);
            }
        },
    },
    created() {
        tnx.deepWatch(this, this.clazz, (newValue, oldValue, path) => {
            if (this.initialized && path === 'refreshTime') {
                this.resetModel();
                // console.info(this.clazz.getName() + ': ' + path + ': ' + tnx.util.string.toJson(
                //     oldValue) + ' -> ' + tnx.util.string.toJson(newValue));
            }
        });
        this.clazz.modified = false;
        tnx.deepWatch(this, this.model, (newValue, oldValue, path) => {
            if (this.initialized && !path.endsWith('.refKind')) {
                this.clazz.modified = true;
                // console.info(this.clazz.getName() + ': ' + path + ': ' + tnx.util.string.toJson(
                //     oldValue) + ' -> ' + tnx.util.string.toJson(newValue));
            }
        });
    },
    mounted() {
        if (this.app.dependencyInitializing) {
            this.$refs.form.disable();
        } else {
            window.addEventListener('keydown', this.onKeydown);
        }
        this.$nextTick(() => {
            setTimeout(() => {
                this.model.properties = this.getModelProperties();
                this.model.indexes = this.getModelIndexes();
                this.showIndexesTable = this.model.indexes.length > 0;
                this.$nextTick(() => {
                    this.expandValueProperty();
                    this.initialized = true;
                    this.$nextTick(() => {
                        setTimeout(() => {
                            this.$emit('initialized', this.clazz);
                        });
                    });
                });
            });
        });
    },
    unmounted() {
        this.clazz.modified = false;
        window.removeEventListener('keydown', this.onKeydown);
    },
    methods: {
        autoTableName() {
            this.model.tableName = this.defaultTableName;
        },
        resetModel() {
            this.lengthDisabled = {};
            this.scaleDisabled = {};
            this.model = this.getModel(true);
            this.expandValueProperty();
        },
        expandValueProperty() {
            this.$nextTick(() => {
                if (this.$refs.propertiesTable && this.model.properties) {
                    for (let property of this.model.properties) {
                        if (!property.embedded?.length) {
                            this.$refs.propertiesTable.toggleRowExpansion(property, true);
                        }
                    }
                }
            });
        },
        getModel(all) {
            return {
                instantiatedGenericTypes: this.clazz.instantiatedGenericTypes ? this.clazz.instantiatedGenericTypes.map(
                    type => type.clone()) : [],
                inheritanceStrategy: this.clazz.inheritanceStrategy?.name || '',
                tableName: this.clazz.tableName || '',
                discriminatorName: this.clazz.discriminatorColumn?.name || '_discriminator',
                discriminatorType: this.clazz.discriminatorColumn?.type?.name || DiscriminatorType.values()[0].name,
                discriminatorLength: this.clazz.discriminatorColumn?.length || 1,
                discriminatorValue: this.clazz.discriminatorValue || '',
                properties: all ? this.getModelProperties() : null,
                indexes: all ? this.getModelIndexes() : null,
            };
        },
        getModelProperties() {
            let modelProperties = [];
            let ordinal = 1;
            for (let classProperty of this.clazz.properties) {
                modelProperties.push(this.toModelProperty(classProperty, ordinal++));
            }

            // 同步所有属性的物理特性
            let allPropertyOrdinals = [];
            for (let modelProperty of modelProperties) {
                allPropertyOrdinals.push(modelProperty.ordinal);
                if (modelProperty.children?.length) {
                    allPropertyOrdinals.push(...modelProperty.children.map(p => p.ordinal));
                }
            }
            this.syncPropertyPhysical(false, ...allPropertyOrdinals);

            return modelProperties;
        },
        toModelProperty(classProperty, ordinal) {
            let mapping = classProperty.mapping;
            let mappingName = mapping?.name || this.getDefaultPhysicalName(classProperty);
            let children = [];
            let refClass = this.app.findClass(classProperty.typeId, true, true);
            if (refClass && TypeKind.VALUE.equals(refClass.kind)) {
                for (let j = 0; j < refClass.properties.length; j++) {
                    let refProperty = refClass.properties[j];
                    let embeddedMapping = classProperty.embedded.find(em => em.logicPropertyId === refProperty.id);
                    if (!embeddedMapping) {
                        embeddedMapping = {};
                    }
                    let refPropertyClass = this.app.findClass(refProperty.typeId, true, true);
                    let refKind = refPropertyClass?.getKind();
                    children.push({
                        id: refProperty.id,
                        parentPropertyName: classProperty.name,
                        ordinal: ordinal + '.' + (j + 1),
                        caption: embeddedMapping.caption || refProperty.caption,
                        name: refProperty.name,
                        typeId: refProperty.typeId,
                        typeName: this.app.getClassName(refProperty.typeId),
                        refKind: refKind,
                        genericTypes: refProperty.genericTypes ? refProperty.genericTypes.map(
                            type => type.clone()) : [],
                        arrayed: refProperty.arrayed,
                        derived: refProperty.derived,
                        color: refProperty.color,
                        manualInput: refProperty.manualInput,
                        regional: refProperty.regional,
                        primary: false,
                        nature: this.getPropertyNature(refProperty.derived, false).name,
                        nature_caption: this.getPropertyNature(refProperty.derived, false).caption,
                        belongs: false,
                        mappingId: embeddedMapping.id || tnx.util.string.uuid32(),
                        mappingName: embeddedMapping.name
                            || (mappingName + '_' + this.getDefaultPhysicalName(refProperty)),
                        mappingType: embeddedMapping.type || PersistUtil.getDefaultPhysicalType(this.app,
                            refProperty.typeId, refProperty.arrayed)?.name || '',
                        mappingLength: embeddedMapping.length || 0,
                        mappingScale: embeddedMapping.scale || 0,
                        mappingNullable: TypeKind.PRIMITIVE.equals(refKind) ? false
                            : (embeddedMapping.nullable || true),
                        mappingDefaultValue: embeddedMapping.defaultValue || '',
                        mappingConverter: embeddedMapping.converter || '',
                        mappingQuerying: this.clazz.keywordMappingIds.includes(embeddedMapping.id) ? {}
                            : embeddedMapping.querying,
                        mappingCommand: embeddedMapping.command,
                    });
                }
            }
            let refKind = refClass?.getKind();
            return {
                id: classProperty.id,
                ordinal: ordinal + '',
                caption: classProperty.caption,
                name: classProperty.name,
                typeId: classProperty.typeId,
                typeName: this.app.getClassName(classProperty.typeId),
                refKind: refKind,
                genericTypes: classProperty.genericTypes,
                arrayed: classProperty.arrayed,
                derived: classProperty.derived,
                color: classProperty.color,
                manualInput: classProperty.manualInput,
                regional: classProperty.regional,
                primary: classProperty.primary,
                nature: this.getPropertyNature(classProperty.derived, classProperty.primary).name,
                nature_caption: this.getPropertyNature(classProperty.derived, classProperty.primary).caption,
                belongs: classProperty.belongs,
                mappingId: mapping?.id || tnx.util.string.uuid32(),
                mappingName: mappingName,
                mappingType: mapping?.type || PersistUtil.getDefaultPhysicalType(this.app,
                    classProperty.typeId, classProperty.arrayed)?.name || '',
                mappingLength: mapping?.length || 0,
                mappingScale: mapping?.scale || 0,
                mappingNullable: TypeKind.PRIMITIVE.equals(refKind) ? false
                    : (mapping?.nullable === undefined ? !classProperty.primary : mapping.nullable),
                mappingDefaultValue: mapping?.defaultValue || '',
                mappingConverter: mapping?.converter || '',
                mappingQuerying: this.clazz.keywordMappingIds.includes(mapping?.id) ? {} : mapping?.querying,
                mappingCommand: mapping?.command,
                children: children,
            }
        },
        getPropertyNature(derived, primary) {
            if (primary) {
                return ClassPropertyNature.PRIMARY;
            }
            if (derived) {
                return ClassPropertyNature.DERIVED;
            }
            return ClassPropertyNature.REGULAR;
        },
        findModelPropertyByOrdinal(ordinal) {
            for (let property of this.model.properties) {
                if (property.ordinal === ordinal) {
                    return property;
                }
                if (property.children?.length && ordinal.contains('.')) {
                    for (let child of property.children) {
                        if (child.ordinal === ordinal) {
                            return child;
                        }
                    }
                }
            }
            return null;
        },
        syncPropertyPhysical(enforced, ...ordinals) {
            this.$nextTick(() => {
                setTimeout(() => {
                    for (let ordinal of ordinals) {
                        let selector = this.$refs['mappingTypeSelect_' + ordinal];
                        if (selector) {
                            let physicalType = selector.currentType;
                            if (physicalType) {
                                let property = this.findModelPropertyByOrdinal(ordinal);
                                this.lengthDisabled[ordinal] =
                                    PhysicalTypeNecessity.NONE.equals(physicalType.lengthNecessity);
                                if (this.lengthDisabled[ordinal]) {
                                    property.mappingLength = 0;
                                } else {
                                    if ((enforced && !this.lengthConfirmed[ordinal]) || !property.mappingLength) {
                                        property.mappingLength = physicalType.defaultLength;
                                    }
                                }

                                this.scaleDisabled[ordinal] =
                                    PhysicalTypeNecessity.NONE.equals(physicalType.scaleNecessity);
                                if (this.scaleDisabled[ordinal]) {
                                    property.mappingScale = 0;
                                } else if (enforced || !property.mappingScale) {
                                    property.mappingScale = physicalType.defaultScale;
                                }
                            }
                        }
                    }
                }, 100);
            });
        },
        newProperty() {
            let nameOrdinal = this.model.properties.length || 2;
            if (TypeKind.RELATION.equals(this.clazz.kind)) {
                nameOrdinal--;
            }
            let name = 'property' + nameOrdinal;
            while (this.model.properties.some(p => p.name === name)) {
                nameOrdinal++;
                name = 'property' + nameOrdinal;
            }
            let property = EntityProperty.of({
                name: name,
                typeId: STRING_CLASS_NAME,
            });
            property.typeName = property.typeId;
            property.ordinal = this.model.properties.length + 1 + '';
            property.refKind = TypeKind.CLASS;
            property.mappingId = tnx.util.string.uuid32();
            property.mappingName = this.getDefaultPhysicalName(property);
            let physicalType = getDefaultPhysicalType(this.app.databaseVendor, property.typeId);
            if (physicalType) {
                property.mappingType = physicalType.name;
            }
            property.mappingNullable = true;
            this.syncPropertyPhysical(true, property.ordinal);
            property.mappingQuerying = {
                name: '',
                typeId: '',
                caption: '',
            }
            property.mappingCommand = {
                name: '',
                typeId: '',
                caption: '',
            }
            return property;
        },
        getDefaultPhysicalName(property) {
            if (!(property instanceof EntityProperty)) {
                let refKind = property.refKind;
                property = EntityProperty.of(property);
                property.refKind = refKind;
            }
            let refClass = this.app.getCollectionElementRefClass(property);
            if (refClass) {
                let associationProperty = refClass.properties.find(p => p.typeId === this.clazz.id);
                if (associationProperty) {
                    property = associationProperty;
                } else if (!TypeKind.RELATION.equals(this.clazz.type)) {
                    // 如果关联类没有引用当前类的属性，则使用当前类的简名+主键字段名构建
                    let primaryProperty = this.clazz.properties.find(p => p.primary);
                    if (primaryProperty) {
                        return this.clazz.simpleName.firstToLowerCase() + '_' + primaryProperty.mapping.name;
                    }
                }
            }
            return property.getDefaultPhysicalFieldName();
        },
        onLogicNameChange(row) {
            // 同步下级属性的上级属性名
            if (row.children) {
                for (let child of row.children) {
                    child.parentPropertyName = row.name;
                }
            }
            row.mappingName = this.getDefaultPhysicalName(row);
            this.onPhysicalNameChange(row);
        },
        onLogicTypeChange(row) {
            delete this.lengthConfirmed[row.ordinal];
            // 单体类的主键类型变更时，引用自身的属性的物理字段类型需同步更新
            if (TypeKind.UNITY.equals(this.clazz.getKind()) && row.primary) {
                for (let property of this.model.properties) {
                    if (property.typeId === this.clazz.id) {
                        property.mappingType = row.mappingType;
                    }
                }
            }
            let refClass = this.app.findClass(row.typeId, true, true);
            row.refKind = refClass?.kind;
            row.mappingName = this.getDefaultPhysicalName(row);
            if (refClass) {
                if (TypeKind.VALUE.equals(row.refKind)) { // 选择的逻辑类型为值模型时，确保子表行
                    if (!row.children?.length) {
                        row.children = [];
                        for (let i = 0; i < refClass.properties?.length; i++) {
                            let childProperty = refClass.properties[i].clone();
                            childProperty.parentPropertyName = row.name;
                            let nature = this.getPropertyNature(childProperty.derived, false);
                            childProperty.nature = nature.name;
                            childProperty.nature_caption = nature.caption;
                            childProperty.ordinal = row.ordinal + '.' + (i + 1);
                            childProperty.mappingName =
                                row.mappingName + '_' + this.getDefaultPhysicalName(childProperty);
                            childProperty.mappingNullable = true;
                            row.children.push(childProperty);
                        }
                        this.syncPropertyPhysical(true, ...row.children.map(p => p.ordinal));
                    }
                    this.$nextTick(() => {
                        if (this.$refs.propertiesTable) {
                            this.$refs.propertiesTable.toggleRowExpansion(row, true);
                        }
                    });
                } else {
                    row.children = [];
                    if (TypeKind.PRIMITIVE.equals(row.refKind)) {
                        row.mappingNullable = false;
                    } else if (TypeKind.ENUM.equals(row.refKind)) {
                        row.mappingType = 'char';
                        let mappingLength = 0;
                        for (let constant of refClass.constants) {
                            if (mappingLength < constant.value.length) {
                                if (mappingLength !== 0) { // 有多个不同长度的值，则类型采用varchar
                                    row.mappingType = 'varchar';
                                }
                                mappingLength = constant.value.length;
                            }
                        }
                        row.mappingLength = mappingLength || 1;
                        this.lengthConfirmed[row.ordinal] = true;
                    }
                }
            } else {
                row.children = [];
            }
        },
        onGenericTypeChange(row) {
            row.mappingName = this.getDefaultPhysicalName(row);

        },
        rowClassName({row}) {
            if (row.parentPropertyName) {
                return 'row-child';
            }
            if (row.derived) {
                return 'row-derived';
            }
            return undefined;
        },
        onNatureChange(row) {
            row.primary = row.nature === ClassPropertyNature.PRIMARY.name;
            row.derived = row.nature === ClassPropertyNature.DERIVED.name;
        },
        onPhysicalNameChange(row) {
            if (row.children) {
                for (let child of row.children) {
                    let names = child.mappingName.split('_');
                    if (names.length === 1) {
                        names.unshift(row.mappingName);
                    } else {
                        names[0] = row.mappingName;
                    }
                    child.mappingName = names.join('_');
                }
            }
        },
        onPhysicalTypeChange(ordinal) {
            if (this.model.properties) {
                let ordinals = typeof ordinal === 'string' ? ordinal.split('.') : [ordinal];
                let index1 = parseInt(ordinals[0]) - 1;
                let property = this.model.properties[index1];
                if (property) {
                    if (ordinals.length > 1) {
                        let index2 = parseInt(ordinals[1]) - 1;
                        property = property.children[index2];
                    }
                    if (property) {
                        this.syncPropertyPhysical(true, property.ordinal);
                    }
                }
            }
        },
        onLengthChange(row) {
            // 单体类的主键长度变更时，引用自身的属性的物理字段长度需同步更新
            if (TypeKind.UNITY.equals(this.clazz.getKind()) && row.primary && this.model.properties) {
                for (let i = 0; i < this.model.properties.length; i++) {
                    let property = this.model.properties[i];
                    if (property.typeId === this.clazz.id) {
                        property.mappingLength = row.mappingLength;
                    }
                }
            }
        },
        isSortableRow(row, index, up) {
            if (row.parentPropertyName) {
                return false;
            }
            if (row.primary) {
                if (TypeKind.UNITY.equals(this.clazz.getKind())) {
                    // 单体的主键不可移动
                    return false;
                } else if (TypeKind.RELATION.equals(this.clazz.getKind())) {
                    // 关系的右键不可下移
                    if (index === 1 && !up) {
                        return false;
                    }
                }
            } else { // 非主键属性的前一个属性如果为主键，则不可上移
                return this.$refs.propertiesTable.locateRow(row, (dataList, index) => {
                    if (up) {
                        let prev = dataList[index - 1];
                        if (prev && prev.primary) {
                            return false;
                        }
                    }
                    return undefined;
                });
            }
            return undefined;
        },
        isRemovableRow(row) {
            // 主键和内嵌值模型属性不可移除
            return !row.primary && !row.parentPropertyName;
        },
        resetOrdinals() {
            if (this.model.properties) {
                for (let i = 0; i < this.model.properties.length; i++) {
                    let property = this.model.properties[i];
                    property.ordinal = (i + 1).toString();
                    if (property.children) {
                        for (let j = 0; j < property.children.length; j++) {
                            property.children[j].ordinal = `${i + 1}.${j + 1}`;
                        }
                    }
                }
            }
        },
        isManualInputEnabled(row) {
            return !row.primary && row.typeId === STRING_CLASS_NAME && !row.arrayed && !row.regional;
        },
        isRegionalEnabled(row) {
            return !row.primary && row.typeId === STRING_CLASS_NAME && !row.arrayed && !row.manualInput;
        },
        isPhysicalTypeDefaultValueEnable(row) {
            if (row.primary || row.derived) {
                return false;
            }
            if (row.mappingConverter) {
                return true;
            }
            return row.refKind && row.refKind.supports('querying');
        },
        isQueryingEnabled(row) {
            // 非关系实体的主键属性不可作为查询条件
            if (row.primary && !TypeKind.RELATION.equals(this.clazz.kind)) {
                return false;
            }
            // 值模型的引用属性不可作为查询条件
            return !row.children?.length;
        },
        toPropertyQuerying(row) {
            if (!this.app.dependencyInitializing) {
                tnx.open(propertyQuerying, {
                    app: this.app,
                    clazz: this.clazz,
                    row: row,
                    after: querying => {
                        row.mappingQuerying = querying;
                    }
                });
            }
        },
        isCommandEnabled(row) {
            // 非关系实体的主键属性不可作为命令模型属性
            if (row.primary && !TypeKind.RELATION.equals(this.clazz.kind)) {
                return false;
            }
            // 值模型的引用属性不可作为命令模型属性
            return !row.children?.length;
        },
        toPropertyCommand(row) {
            if (!this.app.dependencyInitializing) {
                tnx.open(propertyCommand, {
                    app: this.app,
                    clazz: this.clazz,
                    row: row,
                    after: command => {
                        row.mappingCommand = command;
                    }
                });
            }
        },
        getModelIndexes() {
            if (this.clazz.indexes) {
                return this.clazz.indexes.map(index => {
                    return {
                        name: index.name,
                        type: index.type.name,
                        mappingIds: [...index.mappingIds],
                    };
                });
            }
            return [];
        },
        newIndex() {
            return {
                name: '',
                type: PhysicalIndexType.NORMAL.name,
                mappingIds: [],
            };
        },
        toSave() {
            tnx.showLoading();
            this.doSave(() => {
                tnx.closeLoading();
            });
        },
        doSave(callback) {
            this.$refs.form.validate(success => {
                if (success) {
                    let model = Object.assign({}, this.clazz, {
                        inheritanceStrategy: this.model.inheritanceStrategy,
                        tableName: this.model.tableName,
                        properties: [],
                        keywordMappingIds: [],
                        indexes: [],
                    });
                    if (this.superInheritanceStrategyIsSingleTable) {
                        model.discriminatorValue = this.model.discriminatorValue;
                    } else if (this.model.inheritanceStrategy === InheritanceStrategy.SINGLE_TABLE.name) {
                        model.discriminatorColumn = {
                            name: this.model.discriminatorName,
                            type: this.model.discriminatorType,
                            length: this.model.discriminatorLength,
                        };
                    } else {
                        delete model.discriminatorColumn;
                    }
                    for (let property of this.model.properties) {
                        let children = property.children;
                        property = {
                            id: property.id,
                            caption: property.caption,
                            name: property.name,
                            typeId: property.typeId,
                            genericTypes: property.genericTypes,
                            arrayed: property.arrayed,
                            derived: property.derived,
                            color: property.color,
                            manualInput: this.isManualInputEnabled(property) && property.manualInput,
                            regional: this.isRegionalEnabled(property) && property.regional,
                            primary: property.primary,
                            belongs: property.belongs,
                            mapping: {
                                id: property.mappingId,
                                name: property.mappingName,
                                type: property.mappingType,
                                length: property.mappingLength,
                                scale: property.mappingScale,
                                nullable: property.mappingNullable,
                                defaultValue: this.isPhysicalTypeDefaultValueEnable(property)
                                    ? property.mappingDefaultValue : '',
                                converter: property.mappingConverter,
                                querying: this.isQueryingEnabled(property) ? property.mappingQuerying : null,
                                command: this.isCommandEnabled(property) ? property.mappingCommand : null,
                            },
                        };
                        if (property.mapping.querying?.keywordInvolved) {
                            model.keywordMappingIds.push(property.mapping.id);
                            property.mapping.querying = null;
                        }
                        if (children) {
                            property.embedded = [];
                            for (let child of children) {
                                let embeddedMapping = {
                                    logicPropertyId: child.id,
                                    logicPropertyName: child.name,
                                    caption: child.caption,
                                    id: child.mappingId,
                                    name: child.mappingName,
                                    type: child.mappingType,
                                    length: child.mappingLength,
                                    scale: child.mappingScale,
                                    nullable: child.mappingNullable,
                                    defaultValue: this.isPhysicalTypeDefaultValueEnable(child)
                                        ? child.mappingDefaultValue : '',
                                    converter: child.mappingConverter,
                                    querying: this.isQueryingEnabled(child) ? child.mappingQuerying : null,
                                    command: this.isCommandEnabled(child) ? child.mappingCommand : null,
                                };
                                property.embedded.push(embeddedMapping);
                                if (embeddedMapping.querying?.keywordInvolved) {
                                    model.keywordMappingIds.push(embeddedMapping.id);
                                    embeddedMapping.querying = null;
                                }
                            }
                        }
                        model.properties.push(property);
                    }
                    if (this.showIndexesTable) {
                        for (let index of this.model.indexes) {
                            if (!index.mappingIds.length) {
                                tnx.error('索引 ' + index.name + ' 未包含字段');
                                return;
                            }
                            model.indexes.push(index);
                        }
                    }
                    this.saving = true;
                    this.save(this.clazz.id, this.app, model, success => {
                        setTimeout(() => {
                            if (success) {
                                this.clazz.modified = false;
                            }
                            this.saving = false;
                            if (typeof callback === 'function') {
                                callback();
                            }
                        }, 300);
                    });
                } else {
                    callback();
                }
            }, true);

        },
        onKeydown(event) {
            if (event.ctrlKey && event.key.toLowerCase() === 's' && this.active) {
                this.toSave();
            }
        },
        toGenerateCode() {
            if (!this.clazz.modified) {
                // 存在有属性的物理特性未指定，则先保存一次后再打开生成代码的界面
                if (this.clazz.properties.some(property => {
                    if (!property.mapping || (!property.embedded?.length && !property.mapping.type)) {
                        return true;
                    }
                    if (property.embedded?.length) {
                        for (let embeddedMapping of property.embedded) {
                            if (!embeddedMapping.type) {
                                return true;
                            }
                        }
                    }
                    return false;
                })) {
                    this.doSave(() => {
                        this.openGenerateCode();
                    });
                } else {
                    this.openGenerateCode();
                }
            }
        },
        openGenerateCode() {
            tnx.open(generateCode, {
                app: this.app,
                clazz: this.clazz,
            });
        },
    }
}
</script>

<style lang="scss" scoped>
.row-child {
    background-color: var(--el-color-warning-light-9);
}

:deep(.tnxel-edit-table-container) {

    .row-child {
        background-color: var(--el-color-warning-light-9);

        .is-disabled {
            .el-input__wrapper,
            .el-select__wrapper {
                background-color: var(--el-disabled-bg-color);
            }
        }
    }

    .generic-type-select {
        .el-button {
            border-left-color: var(--el-border-color-lighter);
        }
    }

    .el-input-number.is-controls-right {
        .el-input__wrapper {
            padding-right: 32px;
        }

        .el-input-number__increase,
        .el-input-number__decrease {
            width: 24px;
        }

    }

    .advanced-setting {
        display: flex;
        justify-content: center;
        width: 100%;
        margin: 0 8px;

        .col > div {
            height: 20px;
            display: flex;
            align-items: center;

            .el-badge__content.is-dot {
                width: 5px;
                height: 5px;
                right: 4px;
            }
        }

        .is-disabled {
            .el-badge__content.is-dot {
                background-color: var(--el-color-primary-light-5);
            }
        }
    }

}
</style>
